﻿#pragma once

#include <xy/geom/quat.h>
#include <xy/render/painter.h>
#include <xy/utils/scope_profiler.h>
#include <xy/utils/sfinae.h>

#include <array>
#include <map>

namespace xy
{

namespace render
{

struct primitive_point
{
};

struct primitive_line
{
};

struct primitive_triangle
{
};

struct ray
{
    geom::vec3f origin;
    geom::vec3f direction;

    ray(const geom::vec3f &origin, const geom::vec3f &direction) : origin(origin), direction(direction.normalized())
    {
    }

    geom::vec3f at(float t) const
    {
        return origin + direction * t;
    }
};

template <typename T>
concept hittable = requires(T t, const ray &r) {
    { t.intersect_single_sided(r) } -> std::same_as<std::optional<float>>;
    { t.intersect_double_sided(r) } -> std::same_as<std::optional<float>>;
    { t.normal(geom::vec3f{}) } -> std::same_as<geom::vec3f>;
};

// 辅助变量，用于几何着色器
template <typename varying_t, std::size_t in, std::size_t out> struct auxiliary_varyings_t
{
    static constexpr std::size_t inVertexNum = in;
    static constexpr std::size_t outVertexNum = out;

    std::size_t index{0};
    std::array<varying_t, std::max(in, out)> varyings;

    bool emit_vertex(varying_t &&v)
    {
        varyings[index++] = std::move(v);
        return index == in;
    }

    void end_primitive()
    {
        index = 0;
    }
};

// 深度着色器，劫持标准着色器
template <typename base_shader> struct depth_shader
{
    using input_varyings = base_shader::input_varyings;
    using built_in_varyings = base_shader::built_in_varyings;

    // 注意使用原 shader 的 uniform
    static built_in_varyings vertex_shader(const typename base_shader::uniform_varyings &u, const input_varyings &v)
    {
        built_in_varyings ret;
        ret.position = u.shadow * geom::cartesian_to_homogeneous(v.position);
        return ret;
    }

    // 注意使用原 shader 的 uniform
    static color_t fragment_shader(const typename base_shader::uniform_varyings &u, const built_in_varyings &v)
    {
        // 绘制内容不可见 0x00，可改为可见 0xff
        return 0x00ffffff;
    }
};

struct renderer
{
    /**
     * @brief gamma 校正
     *
     * @param color
     * @return color_t
     */
    static color_t gamma(const color_t &color)
    {
        static constexpr float gamma = 1.0f / 2.2f;
        return color_t(255 * std::pow(color.bgra[2] / 255.0f, gamma), 255 * std::pow(color.bgra[1] / 255.0f, gamma),
                       255 * std::pow(color.bgra[0] / 255.0f, gamma), color.bgra[3]);
    }

    /**
     * @brief 计算环境光遮蔽因子
     *
     * @tparam uniform_t
     * @tparam built_in_t
     * @param[in] u
     * @param[in] v
     * @return float
     */
    template <typename uniform_t, typename built_in_t> static float ssao(const uniform_t &u, const built_in_t &v)
    {
        image_t depthMap = u.shadowMap.value();
        geom::vec4f depthCoord = geom::screen_mapping(v.position, depthMap->width, depthMap->height);

        int x = std::round(depthCoord.x());
        int y = std::round(depthCoord.y());

        // 排除边界位置，防止采样越界
        float total = 0.0f;
        if (x > 0 && x < depthMap->width - 1 && y > 0 && y < depthMap->height - 1)
        {
            // 8 条射线采样：相邻 8 个像素
            for (std::size_t i = 0; i < 8; ++i)
            {
                float theta = 2.0f * 3.1415926f * i / 8.0f;
                int x1 = std::round(depthCoord.x() + std::cos(theta));
                int y1 = std::round(depthCoord.y() + std::sin(theta));

                std::uint32_t depth = depthMap.get(x, y);
                std::uint32_t depth1 = depthMap.get(x1, y1);

                // 将深度图看作高度图，利用高度差计算采样方向斜率
                float slope = std::abs((float)depth1 - (float)depth) / 0xffffffff;
                total += 3.1415926f / 2.0f - std::atan(slope);
            }

            total /= (3.1415926f / 2.0f) * 8;
            total = std::pow(total, u.light.occlusion);
        }

        return total;
    }

    /**
     * @brief 计算阴影映射
     *
     * @tparam uniform_t
     * @tparam built_in_t
     * @param[in] u
     * @param[in] v
     * @return true
     * @return false
     */
    template <typename uniform_t, typename built_in_t> static bool shadow(const uniform_t &u, const built_in_t &v)
    {
        image_t shadowMap = u.shadowMap.value();
        geom::vec4f shadowCoord = geom::screen_mapping(u.shadow * geom::cartesian_to_homogeneous(v.fragPos),
                                                       shadowMap->width, shadowMap->height);

        // 注意现在坐标已经是屏幕空间，不要使用 texture() 函数采样！！！
        int x = std::round(shadowCoord.x());
        int y = std::round(shadowCoord.y());

        // 排除超出边界的采样
        if (x >= 0 && x < shadowMap->width && y >= 0 && y < shadowMap->height)
        {
            std::uint32_t shadowZ = u.shadowMap.value().get(x, y);
            std::uint32_t writeZ = geom::depth_mapping(shadowCoord.z(), geom::camera::zNear, geom::camera::zFar);
            if (writeZ > shadowZ)
                return true;
        }
        return false;
    }

    /**
     * @brief 从 darboux 坐标系到标准坐标系的法向变换
     *
     * @tparam uniform_t
     * @tparam built_in_t
     * @param u
     * @param v
     * @return geom::vec3f
     */
    template <typename uniform_t, typename built_in_t>
    static geom::vec3f darboux_mapping(const uniform_t &u, const built_in_t &v)
    {
        // 注意只有模板可以用 requires 语法，所以单独写一个模板函数
        color_t normalColor = renderer::nearest_texture(u.normalMap.value(), v.texcoord);
        auto normal = renderer::normal_mapping(normalColor);

        // 这里有一个节约开销的技巧 https://learnopengl-cn.github.io/05%20Advanced%20Lighting/04%20Normal%20Mapping/
        // 可以将 (gradu, gradv, normal) = TBN 取逆
        // 在顶点着色器中将 normal 以及光照等信息逆变换到切空间中，这样光照就完全在切空间中计算
        // 由于通常顶点着色器比片段着色器执行次数少，因此可以节约计算量
        if constexpr (requires(built_in_t t) {
                          std::is_same_v<decltype(t.gradu), geom::vec3f>;
                          std::is_same_v<decltype(t.gradv), geom::vec3f>;
                      })
            return (v.gradu * normal[0] + v.gradv * normal[1] + v.normal * normal[2]).normalize();

        return normal;
    }

    /**
     * @brief 法向贴图映射 [-1,1] -> [0,255]
     *
     * @param[in] color
     * @return color_t
     */
    static color_t normal_mapping(const geom::vec3f &color)
    {
        return color_t(255 * (color[0] + 1.0f) * 0.5f, 255 * (color[1] + 1.0f) * 0.5f, 255 * (color[2] + 1.0f) * 0.5f,
                       255);
    }

    /**
     * @brief 法向贴图映射 [0,255] -> [-1,1]
     *
     * @param color
     * @return geom::vec3f
     */
    static geom::vec3f normal_mapping(const color_t &color)
    {
        return geom::vec3f{2 * color.bgra[2] / 255.0f - 1, 2 * color.bgra[1] / 255.0f - 1,
                           2 * color.bgra[0] / 255.0f - 1}
            .normalize();
    }

    /**
     * @brief mipmap 采样
     *
     * @tparam uniform_t
     * @tparam built_in_t
     * @param u
     * @param v
     * @return color_t
     */
    template <typename uniform_t, typename built_in_t>
    static color_t mipmap_texture(const uniform_t &u, const built_in_t &v)
    {
        // 注意只有模板可以用 requires 语法，所以单独写一个模板函数
        if constexpr (requires(built_in_t t) { std::is_same_v<decltype(t.sampleDist), float>; })
        {
            auto image = u.diffuseMap.images[0];
            float step = std::min(1.0f / (image->width - 1), 1.0f / (image->height - 1));
            float mipmapLevel = math::clamp(std::log2(v.sampleDist / step), 0.0f, u.diffuseMap.images.size() - 1.0f);

            int level0 = static_cast<int>(mipmapLevel);
            int level1 = static_cast<int>(std::min(level0 + 1.0f, u.diffuseMap.images.size() - 1.0f));
            float t = mipmapLevel - level0;

            color_t color0 = nearest_texture(u.diffuseMap.images[level0], v.texcoord);
            color_t color1 = nearest_texture(u.diffuseMap.images[level1], v.texcoord);
            return color0 * (1 - t) + color1 * t;
        }
        return nearest_texture(u.diffuseMap.images[0], v.texcoord);
    }

    /**
     * @brief 最近邻插值
     * 对于偶数个块，最近邻将得到
     * |00|11|22|33| -> 0|11|22|3 -> 0|11|22|30|11|22|3 ...
     * 对于奇数个块，最近邻将得到
     * |00|11|22| -> 0|11|2 -> 0|11|20|11|2 ...
     * 对于偶数个块，只有 0 == 3 时才是周期纹理
     * 对于奇数个块，只有 0 == 2 时才是周期纹理
     *
     * @param[in] image
     * @param[in] texcoord
     * @return color_t
     */
    static color_t nearest_texture(const image_t &image, const geom::vec2f &texcoord)
    {
        float u = texcoord.x() >= 0 ? std::fmod(texcoord.x(), 1.0f) : 1.0f - std::fmod(-texcoord.x(), 1.0f);
        float v = texcoord.y() >= 0 ? std::fmod(texcoord.y(), 1.0f) : 1.0f - std::fmod(-texcoord.y(), 1.0f);

        // 注意这一部分 u,v 操作补齐了周期性
        u *= image->width;
        v *= image->height;

        u -= 0.5f;
        v -= 0.5f;

        int x0 = static_cast<int>(u);
        int x1 = x0 + 1;
        int y0 = static_cast<int>(v);
        int y1 = y0 + 1;

        int x = (u - x0) < (x1 - u) ? x0 : x1;
        int y = (v - y0) < (y1 - v) ? y0 : y1;

        x %= image->width;
        y %= image->height;

        return image.get(x, y);
    }

    /**
     * @brief 双线性插值，注意会丢失 alpha 通道
     *
     * @param[in] image
     * @param[in] texcoord
     * @return color_t
     */
    static color_t bilinear_texture(const image_t &image, const geom::vec2f &texcoord)
    {
        float u = texcoord.x() > 0 ? std::fmod(texcoord.x(), 1.0f) : 1.0f - std::fmod(-texcoord.x(), 1.0f);
        float v = texcoord.y() > 0 ? std::fmod(texcoord.y(), 1.0f) : 1.0f - std::fmod(-texcoord.y(), 1.0f);

        u *= image->width;
        v *= image->height;

        u -= 0.5f;
        v -= 0.5f;

        int x0 = static_cast<int>(u);
        int x1 = (x0 + 1) % image->width;
        int y0 = static_cast<int>(v);
        int y1 = (y0 + 1) % image->height;

        color_t c00 = image.get(x0, y0);
        color_t c10 = image.get(x1, y0);
        color_t c01 = image.get(x0, y1);
        color_t c11 = image.get(x1, y1);

        float dx = u - x0;
        float dy = v - y0;

        return (c00 * (1 - dx) + c10 * dx) * (1 - dy) + (c01 * (1 - dx) + c11 * dx) * dy;
    }

    /**
     * @brief mipmap 采样
     *
     * @tparam varying_t
     * @param aPainter
     * @param triangle
     * @param v0
     * @param v1
     * @param v2
     * @param a
     * @param b
     * @param c
     * @return float
     */
    template <typename varying_t>
    static float mipmap_level(std::shared_ptr<painter> aPainter, const std::array<geom::vec4f, 3> &triangle,
                              const varying_t &v0, const varying_t &v1, const varying_t &v2, float a, float b, float c)
    {
        geom::vec4f screenPos = geom::screen_mapping(v0.position * a + v1.position * b + v2.position * c,
                                                     aPainter->frame->width, aPainter->frame->height);
        geom::vec4f screenPosX{screenPos.x() + 1, screenPos.y(), screenPos.z(), screenPos.w()};
        geom::vec4f screenPosY{screenPos.x(), screenPos.y() + 1, screenPos.z(), screenPos.w()};

        geom::vec3f coordX = geom::perspective_correct(geom::barycentric(screenPosX, triangle.data()).value(),
                                                       triangle[0].w(), triangle[1].w(), triangle[2].w());
        geom::vec3f coordY = geom::perspective_correct(geom::barycentric(screenPosY, triangle.data()).value(),
                                                       triangle[0].w(), triangle[1].w(), triangle[2].w());

        geom::vec2f tex = v0.texcoord * a + v1.texcoord * b + v2.texcoord * c;
        geom::vec2f texX = v0.texcoord * coordX[0] + v1.texcoord * coordX[1] + v2.texcoord * coordX[2];
        geom::vec2f texY = v0.texcoord * coordY[0] + v1.texcoord * coordY[1] + v2.texcoord * coordY[2];

        return std::max((tex - texX).modulus(), (tex - texY).modulus());
    }

    /**
     * @brief 构建 darboux 标架
     *
     * @tparam varying_t
     * @param v0
     * @param v1
     * @param v2
     * @param normal
     * @return std::array<geom::vec3f, 2>
     */
    template <typename varying_t>
    static std::array<geom::vec3f, 2> tangent_mapping(const varying_t &v0, const varying_t &v1, const varying_t &v2,
                                                      const geom::vec3f &normal)
    {
        geom::vec3f pts[] = {v0.fragPos, v1.fragPos, v2.fragPos};
        auto invA = geom::darboux(pts, normal);
        if (!invA)
            return {geom::vec3f{}, geom::vec3f{}};

        geom::vec3f u{v1.texcoord[0] - v0.texcoord[0], v2.texcoord[0] - v0.texcoord[0], 0.0f};
        geom::vec3f v{v1.texcoord[1] - v0.texcoord[1], v2.texcoord[1] - v0.texcoord[1], 0.0f};

        // darboux 标架
        geom::vec3f gradu = (invA.value() * u).normalize();
        geom::vec3f gradv = (invA.value() * v).normalize();

        return {gradu, gradv};
    }

    /**
     * @brief 背面剔除
     *
     * @param v0
     * @param v1
     * @param v2
     * @return true
     * @return false
     */
    static bool back_face_culling(const geom::vec4f &v0, const geom::vec4f &v1, const geom::vec4f &v2)
    {
        // 注意由于 screen_mapping 将坐标上下翻转，因此这里需要取反
        return (v1.x() - v0.x()) * (v2.y() - v0.y()) - (v2.x() - v0.x()) * (v1.y() - v0.y()) > 0;
    }

    /**
     * @brief 正面剔除
     *
     * @param v0
     * @param v1
     * @param v2
     * @return true
     * @return false
     */
    static bool front_face_culling(const geom::vec4f &v0, const geom::vec4f &v1, const geom::vec4f &v2)
    {
        // 注意由于 screen_mapping 将坐标上下翻转，因此这里需要取反
        return (v1.x() - v0.x()) * (v2.y() - v0.y()) - (v2.x() - v0.x()) * (v1.y() - v0.y()) < 0;
    }

    /**
     * @brief 指定超平面裁剪
     *
     * @tparam varying_t
     * @param[in] varyings
     * @param[in] outVaryings
     * @param[in] inVertexNum
     * @param[in] plane
     * @param[in] sign
     * @return std::size_t
     */
    template <typename varying_t>
    static std::size_t clip_against_plane(const std::array<varying_t, 9> &varyings,
                                          std::array<varying_t, 9> &outVaryings, std::size_t inVertexNum,
                                          std::size_t plane, int sign)
    {
        std::size_t vertexNum = 0;
        for (std::size_t i = 0; i < inVertexNum; i++)
        {
            auto curr = i;
            auto next = (i + 1) % inVertexNum;

            // 检查是否在平面内（不考虑 w 坐标的符号，如果非要取 w > 0，三角形接近镜头时会裁剪出问题）
            bool prevInside = varyings[curr].position[plane] * sign <= varyings[curr].position[3];
            bool currInside = varyings[next].position[plane] * sign <= varyings[next].position[3];

            // 4 种情况
            // curr and next, 记录 curr
            // curr and not next, 记录 curr 和 blend
            // not curr and next, 记录 blend
            // not curr and not next, 跳过
            if (prevInside)
                outVaryings[vertexNum++] = varyings[curr];

            if (prevInside != currInside)
            {
                // 齐次空间裁剪（注意 blend = (1 - t) * start + t * end 也混合了 w 坐标）
                auto t = (varyings[curr].position[plane] - sign * varyings[curr].position[3]) /
                         (varyings[curr].position[plane] - sign * varyings[curr].position[3] -
                          (varyings[next].position[plane] - sign * varyings[next].position[3]));
                outVaryings[vertexNum++] = varying_t::blend(varyings[curr], varyings[next], t);
            }
        }

        return vertexNum;
    }

    /**
     * @brief 齐次空间裁剪
     *
     * @tparam varying_t
     * @param[in] varyings
     * @return std::size_t
     */
    template <typename varying_t> static std::size_t clip_primitive(std::array<varying_t, 9> &varyings)
    {
        // 裁剪顺序：z = -1,, z = 1, y = -1, y = 1, x = -1, x = 1
        std::size_t vertexNum = 3;
        std::array<varying_t, 9> varyings_;

        // 交替容器更新，注意确保返回时，更新的是 varyings
        for (int i = 2; i >= 0; i--)
        {
            vertexNum = clip_against_plane(varyings, varyings_, vertexNum, i, -1);
            vertexNum = clip_against_plane(varyings_, varyings, vertexNum, i, 1);
            if (vertexNum == 0)
                return 0;
        }

        return vertexNum;
    }

    template <typename shader_t>
        requires(std::is_same_v<typename shader_t::primitive_t, primitive_point>)
    static void draw_primitives(std::shared_ptr<painter> aPainter, const shader_t &shader,
                                const typename shader_t::auxiliary_t &auxiliary)
    {
        for (const auto &v : auxiliary.varyings)
        {
            // 由于 draw_point 使用 safe_set，因此不用内部检测，便于绘制具有大小的点
            auto screenPos = geom::screen_mapping(v.position, aPainter->frame->width, aPainter->frame->height);
            aPainter->draw_point(screenPos.data(), shader.uniform.pointSize, shader.depthFunc,
                                 shader.fragmentShader(shader.uniform, v));
        }
    }

    template <typename shader_t>
        requires(std::is_same_v<typename shader_t::primitive_t, primitive_line>)
    static void draw_primitives(std::shared_ptr<painter> aPainter, const shader_t &shader,
                                const typename shader_t::auxiliary_t &auxiliary)
    {
        for (std::size_t i = 0; i < auxiliary.outVertexNum - 1; i += 2)
        {
            // 由于 draw_line 使用 safe_set，因此不用内部检测，便于绘制具有宽度的线
            const auto &v0 = auxiliary.varyings[i];
            const auto &v1 = auxiliary.varyings[i + 1];

            std::array<geom::vec4f, 2> screenPos = {
                geom::screen_mapping(v0.position, aPainter->frame->width, aPainter->frame->height),
                geom::screen_mapping(v1.position, aPainter->frame->width, aPainter->frame->height)};

            aPainter->draw_line_antialias(screenPos[0].data(), screenPos[1].data(), shader.uniform.lineWidth,
                                          shader.depthFunc, [&](float t) -> color_t {
                                              return shader.fragmentShader(shader.uniform,
                                                                           shader_t::built_in_t::blend(v0, v1, t));
                                          });
        }
    }

    template <typename shader_t>
        requires(std::is_same_v<typename shader_t::primitive_t, primitive_triangle>)
    static void draw_primitives(std::shared_ptr<painter> aPainter, const shader_t &shader,
                                const typename shader_t::auxiliary_t &auxiliary)
    {
        std::array<geom::vec4f, 3> triangle;
        for (std::size_t i = 0; i < auxiliary.outVertexNum - 2; i += 3)
        {
            // 10ms
            std::array<typename shader_t::built_in_t, 3 + 6> varyings = {
                auxiliary.varyings[i], auxiliary.varyings[i + 1], auxiliary.varyings[i + 2]};

            // 内部检测，如果不在内部，则裁剪
            bool inside = true;
            for (std::size_t j = 0; j < 3; j++)
                for (std::size_t i = 0; i < 3; i++)
                    inside &= std::abs(varyings[j].position[i]) <= std::abs(varyings[j].position[3]);

            // 40ms
            std::size_t vertexNum = 3;
            if (!inside)
                vertexNum = clip_primitive(varyings);
            if (vertexNum < 3)
                continue;

            std::size_t index0 = 0;
            std::size_t index1 = 1;
            triangle[0] =
                geom::screen_mapping(varyings[index0].position, aPainter->frame->width, aPainter->frame->height);
            triangle[1] =
                geom::screen_mapping(varyings[index1].position, aPainter->frame->width, aPainter->frame->height);

            for (std::size_t j = 2; j < vertexNum; j++)
            {
                std::size_t index2 = j;
                triangle[2] =
                    geom::screen_mapping(varyings[index2].position, aPainter->frame->width, aPainter->frame->height);

                // 背面剔除
                if (!shader.cullingFunc(triangle[0], triangle[1], triangle[2]))
                {
                    // 线框模式
                    if (shader.uniform.mode == shader_t::uniform_t::wireframe)
                    {
                        aPainter->draw_line(
                            triangle[0].data(), triangle[1].data(), shader.depthFunc, [&](float t) -> color_t {
                                return shader.fragmentShader(
                                    shader.uniform, shader_t::built_in_t::blend(varyings[index0], varyings[index1], t));
                            });

                        aPainter->draw_line(
                            triangle[1].data(), triangle[2].data(), shader.depthFunc, [&](float t) -> color_t {
                                return shader.fragmentShader(
                                    shader.uniform, shader_t::built_in_t::blend(varyings[index1], varyings[index2], t));
                            });

                        aPainter->draw_line(
                            triangle[2].data(), triangle[0].data(), shader.depthFunc, [&](float t) -> color_t {
                                return shader.fragmentShader(
                                    shader.uniform, shader_t::built_in_t::blend(varyings[index2], varyings[index0], t));
                            });
                    }
                    else
                    {
                        aPainter->fill_triangle(
                            triangle[0].data(), triangle[1].data(), triangle[2].data(), shader.depthFunc,
                            [&](float a, float b, float c) -> color_t {
                                auto varying = shader_t::built_in_t::blend(varyings[index0], varyings[index1],
                                                                           varyings[index2], a, b, c);

                                // 切空间映射
                                if constexpr (requires(decltype(varying) t) {
                                                  std::is_same_v<decltype(t.gradu), geom::vec3f>;
                                                  std::is_same_v<decltype(t.gradv), geom::vec3f>;
                                                  std::is_same_v<decltype(t.normal), geom::vec3f>;
                                              })
                                {
                                    auto [gradu, gradv] = tangent_mapping(varyings[index0], varyings[index1],
                                                                          varyings[index2], varying.normal);
                                    varying.gradu = gradu;
                                    varying.gradv = gradv;
                                }

                                // 计算 mipmap 采样距离
                                if constexpr (requires(decltype(varying) t) {
                                                  std::is_same_v<decltype(t.sampleDist), float>;
                                              })
                                    varying.sampleDist = mipmap_level(aPainter, triangle, varyings[index0],
                                                                      varyings[index1], varyings[index2], a, b, c);

                                return shader.fragmentShader(shader.uniform, varying);
                            });
                    }
                }

                triangle[1] = std::move(triangle[2]);
                index1 = index2;
            }
        };
    }

    /**
     * @brief 环境光遮蔽计算
     *
     * @tparam shader_t
     * @param[in] aPainter
     * @param[in] shader
     */
    template <typename shader_t> static void draw_ssao(std::shared_ptr<painter> aPainter, const shader_t &shader)
    {
        // 切换为法向模式来绘制深度图（不切 smooth 是因为后者需要绘制阴影，而此时深度图可能还未创建）
        const_cast<shader_t &>(shader).uniform.mode = shader_t::uniform_t::normal;

        typename shader_t::auxiliary_t auxiliary;
        for (const auto &v : shader.varyings)
        {
            // 30ms
            if (auxiliary.emit_vertex(shader.vertexShader(shader.uniform, v)))
            {
                shader.geometryShader(shader.uniform, auxiliary);
                auxiliary.end_primitive();
                draw_primitives(aPainter, shader, auxiliary);
            }
        }

        // 还原模式
        const_cast<shader_t &>(shader).uniform.mode = shader_t::uniform_t::ssao;

        // 初始创建深度贴图
        if (!shader.uniform.shadowMap)
            const_cast<shader_t &>(shader).uniform.shadowMap =
                image_t{aPainter->zBuffer->width, aPainter->zBuffer->height, aPainter->zBuffer->bpp};

        // 拷贝 zBuffer
        std::copy(aPainter->zBuffer->data.begin(), aPainter->zBuffer->data.end(),
                  const_cast<shader_t &>(shader).uniform.shadowMap.value()->data.begin());

        // 清空 zBuffer（如果不是线框模式，颜色缓冲可以不清空）
        aPainter->clear_color(0xff000000);
        aPainter->clear_depth(0xffffffff);
    }

    /**
     * @brief 阴影映射计算
     *
     * @tparam shader_t
     * @param[in] aPainter
     * @param[in] shader
     */
    template <typename shader_t> static void draw_shadow(std::shared_ptr<painter> aPainter, const shader_t &shader)
    {
        using depth_shader_t = depth_shader<typename shader_t::shader_t>;

        // 切换到光源位置
        auto right = geom::cross(shader.uniform.light.direction, geom::vec3f{0.0f, 0.0f, 1.0f}).normalize();
        auto up = geom::cross(right, shader.uniform.light.direction).normalize();

        // 获得变换矩阵
        const_cast<shader_t &>(shader).uniform.shadow =
            shader.uniform.projection * geom::look_at(shader.uniform.light.position,
                                                      shader.uniform.light.position + shader.uniform.light.direction,
                                                      up);

        // 强行替换掉 fragmentShader 和隐藏面剔除
        auto fragmentShader = shader.fragmentShader;
        auto cullingFunc = shader.cullingFunc;
        const_cast<shader_t &>(shader).fragmentShader = depth_shader_t::fragment_shader;
        const_cast<shader_t &>(shader).cullingFunc = front_face_culling;

        // 绘制深度贴图
        typename shader_t::auxiliary_t auxiliary;
        for (const auto &v : shader.varyings)
        {
            // 替换掉 vertexShader
            if (auxiliary.emit_vertex(depth_shader_t::vertex_shader(shader.uniform, v)))
            {
                shader.geometryShader(shader.uniform, auxiliary);
                auxiliary.end_primitive();
                draw_primitives(aPainter, shader, auxiliary);
            }
        }

        // 还原 fragmentShader 和 隐藏面剔除
        const_cast<shader_t &>(shader).fragmentShader = fragmentShader;
        const_cast<shader_t &>(shader).cullingFunc = cullingFunc;

        // 初始创建阴影贴图
        if (!shader.uniform.shadowMap)
            const_cast<shader_t &>(shader).uniform.shadowMap =
                image_t{aPainter->zBuffer->width, aPainter->zBuffer->height, aPainter->zBuffer->bpp};

        // 拷贝 zBuffer
        std::copy(aPainter->zBuffer->data.begin(), aPainter->zBuffer->data.end(),
                  const_cast<shader_t &>(shader).uniform.shadowMap.value()->data.begin());

        // 清空 zBuffer（如果不是线框模式，颜色缓冲可以不清空）
        aPainter->clear_color(0xff000000);
        aPainter->clear_depth(0xffffffff);
    }

    /**
     * @brief 标准渲染调用接口
     *
     * @tparam shader_t
     * @param aPainter
     * @param shader
     */
    template <typename shader_t> static void draw(std::shared_ptr<painter> aPainter, const shader_t &shader)
    {
        if constexpr (requires {
                          typename depth_shader<typename shader_t::shader_t>;
                          std::declval<typename shader_t::uniform_t>().light;
                          std::declval<typename shader_t::uniform_t>().shadowMap;
                      })
        {
            if (shader.uniform.mode == shader_t::uniform_t::smooth)
                draw_shadow(aPainter, shader);
            else if (shader.uniform.mode == shader_t::uniform_t::ssao)
                draw_ssao(aPainter, shader);
        }

        // 正常绘制
        typename shader_t::auxiliary_t auxiliary;
        for (const auto &v : shader.varyings)
        {
            // 30ms
            if (auxiliary.emit_vertex(shader.vertexShader(shader.uniform, v)))
            {
                shader.geometryShader(shader.uniform, auxiliary);
                auxiliary.end_primitive();
                draw_primitives(aPainter, shader, auxiliary);
            }
        }
    }

    /**
     * @brief 标准光线追踪调用接口
     *
     * @tparam raytracer_t
     * @param raytracer
     * @param image
     */
    template <typename raytracer_t> static void draw(const raytracer_t &raytracer, image_t &image)
    {
        // 1/2 高度
        float fovy = raytracer.uniform.fovy;
        float zNear = raytracer.uniform.zNear;
        float aspect = (float)image->width / (float)image->height;
        float h = std::tan(math::degrees_to_radians(fovy) / 2) * zNear;
        float w = h * aspect;

        // 从屏幕空间映射到近平面
        auto screenMap = [w, h, zNear, &image](float x, float y) -> geom::vec3f {
            return {(2.0f * x / (image->width - 1) - 1.0f) * w, (1.0f - 2.0f * y / (image->height - 1)) * h, -zNear};
        };

        auto rotation = geom::quat<float>::from_to(geom::vec3f{0.0f, 0.0f, -1.0f}, raytracer.uniform.viewDir);
        std::size_t spp = raytracer.uniform.samplePerPixel;

#pragma omp parallel for collapse(2)
        for (std::size_t j = 0; j < image->height; j++)
        {
            for (std::size_t i = 0; i < image->width; i++)
            {
                float step = 1.0f / (spp + 1);
                float weight = 1.0f / (spp * spp);

                color_t total(0, 0, 0, 255);
                for (std::size_t k = 1; k <= spp; k++)
                {
                    for (std::size_t l = 1; l <= spp; l++)
                    {
                        geom::vec3f lookat = screenMap(i + k * step, j + l * step);
                        ray r(raytracer.uniform.viewPos, rotation * lookat);

                        color_t color(0, 0, 0, 255);
                        for (const auto &l : raytracer.lights)
                            color = color + raytracer.raycaster(raytracer.objects, l, r, raytracer.uniform.bg,
                                                                raytracer.uniform.maxDepth);
                        total = total + color * weight;
                    }
                }
                image.set(i, j, total);
            }
        }
    }
};

} // namespace render

} // namespace xy
